package handler

import (
	"focus-single/internal/packed/event"
	message2 "focus-single/internal/packed/message"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/util/gconv"
	"log"
	"net/http"
	"time"

	"github.com/lxzan/gws"
)

const (
	PingInterval         = 60 * time.Second // 客户端心跳间隔
	HeartbeatWaitTimeout = 60 * time.Second // 心跳等待超时时间
	WebSocketKey         = "Sec-ClientManager-Key"
	ClientIdKey          = "clientId" // 代表客户端Id,不同链接的客户端Id不同
)

var grader = CreateUpGrader()

func CreateUpGrader() *gws.Upgrader {
	return gws.NewUpgrader(NewClientManager(), &gws.ServerOption{
		PermessageDeflate: gws.PermessageDeflate{
			Enabled:               true,
			ServerContextTakeover: true, // 服务器上下文接管
			ClientContextTakeover: true, // 客户端上下文结果
		},
		// 授权:在querystring里面传入客户Id
		// 把Sec-ClientManager-Key作为连接的key
		// 刷新页面的时候,会触发上一个连接的OnClose/OnError事件,这时候需要对比key并删除map里存储的连接
		Authorize: func(r *http.Request, session gws.SessionStorage) bool {
			if clientId := r.URL.Query().Get(ClientIdKey); clientId != "" {
				log.Println("接收到握手成功:", r.RemoteAddr)
				session.Store(ClientIdKey, clientId) // 客户端链接Id
				session.Store(WebSocketKey, r.Header.Get(WebSocketKey))
				return true
			} else {
				log.Println("接收到握手失败:必须传递clientId参数")
				return false
			}
		},
	})
}

func ClientHandler(request *ghttp.Request) {
	if socket, err := grader.Upgrade(request.Response.Writer, request.Request); err == nil {
		go socket.ReadLoop()
		log.Println("websocket握手成功.进入ReadLoop")
	} else {
		log.Println("websocket握手失败:", err)
		return
	}
}

// Broadcast 广播消息
//func Broadcast(sockets []*gws.Conn, opcode gws.Opcode, payload []byte) {
//	var b = gws.NewBroadcaster(opcode, payload)
//	defer func(b *gws.Broadcaster) {
//		_ = b.Close()
//	}(b)
//	for _, item := range sockets {
//		_ = b.Broadcast(item)
//	}
//}

// MustLoad 加载指定用户的链接中的键值对
func MustLoad[T any](session gws.SessionStorage, key string) (v T) {
	if value, exist := session.Load(key); exist {
		v = value.(T)
	}
	return
}

// ClientManager 管理所有链接的客户端(只创建唯一的1个)
type ClientManager struct {
	clients *gws.ConcurrentMap[string, *Client] // 使用内置的ConcurrentMap存储客户端链接, 可以减少锁冲突(user,conn)
}

func NewClientManager() *ClientManager {
	return &ClientManager{
		// 在内存中保存指定用户的链接key=userId,value=conn
		clients: gws.NewConcurrentMap[string, *Client](16, 128),
	}
}

// OnOpen 客户端打开链接
func (manager *ClientManager) OnOpen(socket *gws.Conn) {
	clientId := MustLoad[string](socket.Session(), ClientIdKey) // 当前上下文中获取用户名
	// 如果该用户名存在链接.则关闭原有链接.然后重新打开新链接并保存
	if client, ok := manager.clients.Load(clientId); ok {
		client.Close()
	}
	_ = socket.SetDeadline(time.Now().Add(PingInterval + HeartbeatWaitTimeout))
	// 构造新的客户端
	client := NewClient(clientId, socket, uint64(time.Now().Unix()))
	manager.clients.Store(clientId, client)
	go client.WriteLoop() // TODO 写循环写入数据到客户端
	log.Printf("打开链接:%s connected,并开始处理消息循环.\n", clientId)
}

// OnClose 客户端关闭链接或者错误关系
func (manager *ClientManager) OnClose(socket *gws.Conn, err error) {
	clientId := MustLoad[string](socket.Session(), ClientIdKey) // 当前上下文中clientId对应的Key
	sharding := manager.clients.GetSharding(clientId)           // 取客户端对应的链接
	sharding.Lock()
	defer sharding.Unlock()

	if client, ok := sharding.Load(clientId); ok {
		key0 := MustLoad[string](socket.Session(), WebSocketKey)
		if key1 := MustLoad[string](client.Socket.Session(), WebSocketKey); key1 == key0 {
			client.Close()
			sharding.Delete(clientId)
		}
	}
	log.Printf("关闭链接:OnClose, clientId=%s, msg=%s\n", clientId, err.Error())
}

// OnPing 接收到客户端的ping请求
func (manager *ClientManager) OnPing(socket *gws.Conn, payload []byte) {
	log.Println("处理ping消息:", string(payload))
	// Deadline:最后期限,截止日期
	_ = socket.SetDeadline(time.Now().Add(PingInterval + HeartbeatWaitTimeout))
	log.Println("回复pong消息")
	_ = socket.WriteString(`{"event":"pong"}`)
}

// OnPong 接收到客户端的Pong请求
func (manager *ClientManager) OnPong(socket *gws.Conn, payload []byte) {
	log.Println("处理pong消息:", string(payload))
}

// OnMessage 接收到客户端的消息
func (manager *ClientManager) OnMessage(socket *gws.Conn, message *gws.Message) {
	defer func() {
		_ = message.Close()
	}()
	clientId := MustLoad[string](socket.Session(), ClientIdKey)
	log.Println("接收到消息", clientId, string(message.Bytes()))
	// chrome websocket不支持ping方法, 所以在text frame里面模拟ping
	request := &message2.WRequest{}
	_ = gconv.Struct(message.Bytes(), request)
	switch request.Event {
	case event.PING:
		manager.OnPing(socket, message.Bytes())
	case event.CHAT:
		// 把接收到的消息发送给指定的(user/tag/all)
		response := &message2.WResponse{}
		response.Event = request.Event
		response.Data = request.Data
		if userId := request.Data["userId"]; userId != nil {
			log.Println("发送消息到userId:", userId.(string))
			manager.PutUser(userId.(string), response)
		} else if clientId := request.Data["clientId"]; clientId != nil {
			log.Println("发送消息到clientId:", clientId.(string))
			manager.PutClient(clientId.(string), response)
		} else if tag := request.Data["tag"]; tag != nil {
			log.Println("发送消息到tag:", tag.(string))
			manager.PutTag(tag.(string), response)
		} else {
			log.Println("没有找到指定的接收者")
		}
	case event.LOGIN: // 注册用户
		log.Println("登录事件")
		if client, ok := manager.clients.Load(clientId); ok {
			if userId := request.Data["userId"]; userId != nil {
				client.UserId = userId.(string)
				log.Println("用户登录成功", client.UserId)
			}
		}
	case event.LOGOUT: // 注销用户
		log.Println("注销事件")
	case event.JOINTAG: // 加入tag
		log.Println("加入Tag事件")
		if client, ok := manager.clients.Load(clientId); ok {
			if tag := request.Data["tag"]; tag != nil {
				client.Tags.Add(tag.(string))
				log.Println("tag加入成功", tag)
			}
		}
	case event.LEAVETAG: // 离开tag
		log.Println("离开Tag事件")
		if client, ok := manager.clients.Load(clientId); ok {
			if tag := request.Data["tag"]; tag != nil {
				client.Tags.Remove(tag.(string))
				log.Println("tag离开成功", tag)
			}
		}
	}
}

// Len 统计当前在线用户
func (manager *ClientManager) Len() int {
	return manager.clients.Len()
}
func (manager *ClientManager) findClient(clientId string) *Client {
	if client, exist := manager.clients.Load(clientId); exist {
		return client
	}
	return nil
}

// PutClient 把消息放到指定客户端的发送通道中
func (manager *ClientManager) PutClient(clientId string, message *message2.WResponse) {
	client := manager.findClient(clientId)
	if client != nil {
		client.Put(message)
	}
}

// FindClientByUser 查找指定用户对应的客户端(可能是多个)
func (manager *ClientManager) FindClientByUser(userId string) map[string]*Client {
	clients := make(map[string]*Client)
	manager.clients.Range(func(clientId string, client *Client) (result bool) {
		if client.UserId == userId {
			clients[clientId] = client
		}
		return true
	})
	return clients
}

// PutUser 把消息放到指定UserId对应客户端的发送通道中
func (manager *ClientManager) PutUser(userId string, message *message2.WResponse) {
	clients := manager.FindClientByUser(userId)
	for _, client := range clients {
		client.Put(message)
	}
}

// FindClientByTag 查找指定Tag对应的客户端(可能是多个)
func (manager *ClientManager) FindClientByTag(tag string) map[string]*Client {
	clients := make(map[string]*Client)
	manager.clients.Range(func(clientId string, client *Client) (result bool) {
		if client.Tags.Contains(tag) {
			clients[clientId] = client
		}
		return true
	})
	return clients
}

// PutTag 把消息放到指定Tag对应客户端的发送通道中
func (manager *ClientManager) PutTag(tag string, message *message2.WResponse) {
	clients := manager.FindClientByTag(tag)
	for _, client := range clients {
		client.Put(message)
	}
}
